/* *********************************************
 * GazeVis
 * 
 * *********************************************
 */
 
static final int PHASE_QUESTION_SHOWN	= 1;
static final int PHASE_VIS_SHOWN		= 2;
static final int PHASE_TRANSITION_SHOWN	= 3;

// 
// class StimulusRecord
// ________________________________________________________________________________________________________
class StimulusRecord
{
  int number;
  int questionTimestamp;
  int visTimestamp;
  int responseTimestamp;
  int phase;
  
  int beginT, endT;

  StimulusRecord( int number )
  {
    this.number = number;
  }
	
  void calcTimeRange()
  {
    beginT = questionTimestamp;
    endT = responseTimestamp;
  }
}

// 
// class VisLog
// ________________________________________________________________________________________________________
class VisLog
{
  String logFile;
  ArrayList<StimulusRecord> stimuli;
  int lastT;
		
	VisLog(String logFile)
	{
		this.logFile = logFile;
		stimuli = new ArrayList<StimulusRecord>();
		
		loadVisLog();
	}
	
  boolean loadVisLog()
  {
		CachedFileReader input = new CachedFileReader( logFile, null );
		if (! input.isReady())
		{
			println("Could not open file " + logFile);
			return false;
		}
		
		// stimulus record
		StimulusRecord lastRecord = null;
		
		// file reading
		String l = null;
		while ( null != (l = input.readLine()) )
		{
			
			l = trim(l);
			if (l.length() == 0) {
				continue;
			}
			else if (l.charAt(0) == '#') {
				// ignore comments
				continue;
			}
			
			ArrayList<String> tokens = tokenize(l);
			if (tokens.get(0).compareToIgnoreCase("stimulus") == 0)
			{
				if (lastRecord != null)
				{		
					lastRecord.calcTimeRange();
					stimuli.add( lastRecord );
				}
				lastRecord = new StimulusRecord( Integer.parseInt(tokens.get(1)) );
			}
			else if (tokens.get(0).compareToIgnoreCase("END") == 0)
			{
				lastT = parseTimestamp( tokens.get(1) );
			}
			else
			{
				String strTimestamp = tokens.get(0);
				String event = tokens.get(1);
				
				if (event.compareToIgnoreCase("question") == 0)
				{
					lastRecord.questionTimestamp = parseTimestamp( strTimestamp );
				}
				else if (event.compareToIgnoreCase("vis") == 0)
				{
					lastRecord.visTimestamp = parseTimestamp( strTimestamp );
				}
				else if (event.compareToIgnoreCase("response") == 0)
				{
					lastRecord.responseTimestamp = parseTimestamp( strTimestamp );
				}
			}
		}
		
		if (lastRecord != null)
		{
			lastRecord.calcTimeRange();
			stimuli.add( lastRecord );
		}
		return true;
	}
	
	int getLastTimestamp()
	{
		return lastT;
	}
	
	StimulusRecord getStimulus(int t)
	{
		int s = -1;
		StimulusRecord stimulus = null;
		
		for (int i = 0; i < stimuli.size(); i++)
		{
			stimulus = stimuli.get(i);
			if (t >= stimulus.beginT && t <= stimulus.endT)
			{
				s = i;
				break;
			}
		}
		
		if (s != -1)
		{
			if (t > stimulus.endT) {
				return null;
			}
			else
			{
				return stimulus;
			}
		}
		else
		{
			return null;
		}
	}

	void chopGazeData( GazeLog gazeData, int beginT )
	{
		// create new heat map
		heatmaps = new HashMap< Integer, Heatmap >();
		
		// loop through stimuli
		for (int i = 0; i < stimuli.size(); i++)
		{
			StimulusRecord record = stimuli.get( i );
			
			int qesB = record.questionTimestamp + beginT;
			int qesE = record.visTimestamp + beginT;
			
			int visB = record.visTimestamp + beginT;
			int visE = record.responseTimestamp + beginT;
			
			// write data			
			String[] qesEyeData = gazeData.getFixationsData( qesB, qesE );
			String[] visEyeData = gazeData.getFixationsData( visB, visE );
			
			String qesFile = "data/" + subject_directory + "/question_" + record.number + ".txt";
			String visFile = "data/" + subject_directory + "/stimulus_" + record.number + ".txt";
			String heatmapFile = dataPath( subject_directory + "/heatmap_" + record.number + ".dat" );
			
			saveStrings( qesFile, visEyeData );
			saveStrings( visFile, visEyeData );
			
			// make heatmap
			println(" -- Making heatmap for stimulus: " + record.number);
			Heatmap theMap = new Heatmap( gazeData, record.number );
			theMap.aggregate( gazeData.seek(visB), gazeData.seek(visE) );
			
			// save the heatmap as a binary file
			if (write_heatmaps)
			{
				theMap.write( heatmapFile );
			}
			
			heatmaps.put( record.number, theMap );
	
		}
	}
	
}

// 
// class VisVis
// ________________________________________________________________________________________________________
PFont fontSmallQuestion = null;
static final float SMALL_QUESTION_SIZE = 15.0;

class VisVis
{
	VisLog visLog;
	StimuliList stimuliList;
	Stimulus theStimulus;
	
	int curT;
	int nextT;
	StimulusRecord curStimulus;
	int state;
	
	VisVis( String stimuli_list_file, String visLogFile )
	{
		this.visLog = new VisLog( visLogFile );
		this.stimuliList = new StimuliList( stimuli_list_file  );
		
		println("Loaded stimuli list with: " + stimuliList.size() + " elements.");
		
		curT = 0;
		nextT = 0;
		curStimulus = null;
		theStimulus = null;
		
		// paused, initially
		state = STATE_PAUSED;
		
		if (fontSmallQuestion == null) {
			fontSmallQuestion = createFont("HelveticaBold", SMALL_QUESTION_SIZE);
		}
	}
	
	void setPlayState( int s )
	{
		state = s;
	}
	
	int getPlayState()
	{
		return state;
	}
	
	boolean update( int deltaMillis )
	{
		boolean stopPlay = false;
		int newT = nextT + round((float) deltaMillis * timeScale);
		if (newT == curT)
		{
			return false;
		}
		
		if (newT < 0)
		{
			newT = 0;
		}
		else if ( newT >= visLog.getLastTimestamp() )
		{
			newT = visLog.getLastTimestamp();
			stopPlay = true;
		}	
		
		if ( curStimulus != null && newT >= curStimulus.beginT && newT <= curStimulus.endT)
		{
			// keep current stimulus
		}
		else
		{
			// transition to a new one
			curStimulus = visLog.getStimulus( curT );
			if ( curStimulus != null)
			{
				theStimulus = stimuliList.get( curStimulus.number );
			}
		}
		
		// update timestamps
		curT = newT;
		nextT = newT;
		
		// modulate the stimulus phase
		if ( curStimulus != null)
		{
			if ( curT < curStimulus.visTimestamp )
			{
				theStimulus.state = PHASE_QUESTION;
				curStimulus.phase = PHASE_QUESTION;
			}
			else if (curT >= curStimulus.visTimestamp)
			{
				theStimulus.state = PHASE_VIS;
				curStimulus.phase = PHASE_VIS;
			}
		}
		else
		{
			theStimulus = null;
		}
		return stopPlay;
	}
	
	int getStimState()
	{
		if( theStimulus != null ) return theStimulus.state;
		else return 0;
	}
	
	void updateT( int t )
	{
		nextT = t;
		if (nextT < 0) {
			nextT = 0;
		}
		else if (nextT > visLog.getLastTimestamp())
		{
			nextT = visLog.getLastTimestamp();
		}
		update(0);
	}
	
	boolean render( int deltaMillis )
	{
		boolean stopPlay = false;
		if (state == STATE_PLAYING)
		{
			// update the time
			if (update(deltaMillis))
			{
				state = STATE_PAUSED;
				stopPlay = true;
			}
		}
		
		if (theStimulus == null)
		{
			// render transition
			pushStyle();
			{
				fill(0);
				noStroke();
				rect(0, 0, VIS_WIDTH, VIS_HEIGHT);
			
				fill(255);
				ellipse( VIS_WIDTH / 2, VIS_HEIGHT / 2, 40, 40 );				
			}
			popStyle();
		}
		else
		{
			// render the stimulus
			theStimulus.render();
			
			// render overlay
			fill(0, 0, 0, 60);
			rect(0, 0, VIS_WIDTH, VIS_HEIGHT);
			
			// if in vis mode, render the question in the bottom right corner
			if (theStimulus.state == PHASE_VIS)
			{
				pushStyle();
				
				textFont(fontSmallQuestion);
				textAlign(RIGHT, BOTTOM);
				fill(0);
				
				text( theStimulus.question, VIS_WIDTH - 10, VIS_HEIGHT - 5 );
				
				popStyle();
				
			}
		}
		return stopPlay;
	}
	
	StimulusRecord getCurStimulus()
	{
		return curStimulus;
	}
	
	int getCurrentT()
	{
		return curT;
	}
}

int parseTimestamp( String strTimestamp )
{
	String[] tokens = split(strTimestamp, ".");
	return Integer.parseInt( tokens[0] ) * 1000 + Integer.parseInt( tokens[1] );
}

